package org.spigotmc;

/**
 * Keeps track of the time spent doing main thread activities that can be spread across ticks,
 * so that such work doesn't exceed the current tick's estimated available slack time. Each
 * activity is allotted a proportion of the expected slack time according to its weight, versus the
 * estimated total weight of all activities.
 */
public class SlackActivityAccountant {
    private double prevTickSlackWeightReciprocal = 1 / MIN_SLACK_WEIGHT;
    private static final double MIN_SLACK_WEIGHT = 1 / 65536.0;
    private double averageTickNonSlackNanos = 0;
    private static final double AVERAGING_FACTOR = 0.375;

    private long currentActivityStartNanos;
    private static final long OFF = -1;
    private long currentActivityEndNanos;
    private double tickSlackWeight;
    private long tickSlackNanos;

    private double getSlackFraction(double slackWeight) {
        return Math.min(slackWeight * this.prevTickSlackWeightReciprocal, 1);
    }

    private int getEstimatedSlackNanos() {
        return (int) Math.max(net.minecraft.server.MinecraftServer.currentTick - (long) this.averageTickNonSlackNanos, 0);
    }

    public void tickStarted() {
        this.currentActivityStartNanos = OFF;
        this.tickSlackWeight = 0;
        this.tickSlackNanos = 0;
    }

    public void startActivity(double slackWeight) {
        double slackFraction0 = getSlackFraction(this.tickSlackWeight);
        this.tickSlackWeight += slackWeight;
        double slackFraction1 = getSlackFraction(this.tickSlackWeight);

        long t = System.nanoTime();
        this.currentActivityStartNanos = t;
        this.currentActivityEndNanos = t + ((int) ((slackFraction1 - slackFraction0) * this.getEstimatedSlackNanos()));
    }

    private void endActivity(long endNanos) {
        this.tickSlackNanos += endNanos - this.currentActivityStartNanos;
        this.currentActivityStartNanos = OFF;
    }

    public boolean activityTimeIsExhausted() {
        if (this.currentActivityStartNanos == OFF) {
            return true;
        }

        long t = System.nanoTime();
        if (t <= this.currentActivityEndNanos) {
            return false;
        } else {
            this.endActivity(this.currentActivityEndNanos);
            return true;
        }
    }

    public void endActivity() {
        if (this.currentActivityStartNanos == OFF) {
            return;
        }

        this.endActivity(Math.min(System.nanoTime(), this.currentActivityEndNanos));
    }

    public void tickEnded(long tickNanos) {
        this.prevTickSlackWeightReciprocal = 1 / Math.max(this.tickSlackWeight, MIN_SLACK_WEIGHT);

        long tickNonSlackNanos = tickNanos - this.tickSlackNanos;
        this.averageTickNonSlackNanos = this.averageTickNonSlackNanos * (1 - AVERAGING_FACTOR) + tickNonSlackNanos * AVERAGING_FACTOR;
    }
}
